#!/usr/bin/env bash
#set -x

# For the license, see the LICENSE file in the root directory.

if [ "$(id -u)" -ne 0 ]; then
	echo "Need to be root to run this test."
	exit 77
fi

tmp="$(getenforce 2>&1)"
if [ "${tmp}" = "Enforcing" ]; then
	echo "Test may not work with SELinux in enforcing mode."
	exit 77
fi

# tpm2_ptool may not be packaged everywhere ...
if [ -z "$(type -P tpm2_ptool)" ]; then
	echo "Could not find tpm2_ptool in PATH"
	exit 77
fi

if ! tpm2_ptool | grep -q ",config,"; then
	echo "tpm2_ptool does not support the config command"
	exit 77
fi

if [ -z "$(type -P tpm2-abrmd)" ]; then
	echo "Could not find tpm2-abrmd in PATH"
	exit 77
fi

if [ ! -r /usr/lib64/pkcs11/libtpm2_pkcs11.so ]; then
	echo "/usr/lib64/pkcs11/libtpm2_pkcs11.so is missing"
	echo "tpm2-pkcs11 package may not be installed."
	exit 77
fi

ROOT=${abs_top_builddir:-$(dirname "$0")/..}
TESTDIR=${abs_top_testdir:=$(dirname "$0")}
SRCDIR=${abs_top_srcdir:-$(dirname "$0")/..}

source "${TESTDIR}/common"
SWTPM_CREATE_TPMCA=${SRCDIR}/samples/swtpm-create-tpmca

SWTPM_INTERFACE=socket+socket
SWTPM_SERVER_NAME=localhost
SWTPM_SERVER_PORT=65455
SWTPM_CTRL_PORT=65454
SWTPM_FAKE_CTRL_PORT=65456

workdir="$(mktemp -d)" || exit 1

SWTPM_LOCALCA_DIR="${workdir}/my localca"
SWTPM_LOCALCA_CONF="${workdir}/my localca/swtpm-localca.conf"
export TPM2_PKCS11_STORE="${workdir}"
TPM2_ABRMD_PIDFILE="${workdir}/tpm2-abrmd.pid"

PID="" # primary object id returned by tpm2_ptool
TPM2_ABRMD_PID=""

function cleanup()
{
	if [ -n "${PID}" ]; then
		echo "y" | tpm2_ptool destroy "${PID}" &>/dev/null
	fi
	if [ -n "${TPM2_ABRMD_PID}" ]; then
		kill_quiet -9 "${TPM2_ABRMD_PID}"
	fi
	if [ -n "${SWTPM_PID}" ]; then
		kill_quiet -9 "${SWTPM_PID}"
	fi
	if [ -n "${BASH_PID}" ]; then
		kill_quiet -9 "${BASH_PID}"
	fi
	if [ -n "${NCAT_PID}" ]; then
		kill_quiet -9 "${NCAT_PID}"
	fi
	rm -rf "${workdir}"
}

trap "cleanup" SIGTERM EXIT
source "${TESTDIR}/common"

PATH=${ROOT}/src/swtpm_bios:${ROOT}/src/swtpm_cert:${PATH}

# Run the tests
# @param1: The vTPM for which the certificate is created is a TPM 2
# @param2: The key algorithm for the created TPM 2 CA
function run_test() {
	local vtpm_is_tpm2="$1"
	local algorithm="$2"

	local tmp params certinfo regex regexs fil i skip exp_certsize

	rm -rf "${workdir:?}"/*

	cat <<_EOF_ > "${workdir}/swtpm_setup.conf"
create_certs_tool=${SWTPM_LOCALCA}
create_certs_tool_config=${workdir}/swtpm-localca.conf
create_certs_tool_options=/dev/null
_EOF_

	if ! ${SWTPM_SETUP} \
		--tpm-state "${workdir}" \
		--logfile "${workdir}/logfile" \
		--config "${workdir}/swtpm_setup.conf" \
		--tpm "${SWTPM_EXE} socket ${SWTPM_TEST_SECCOMP_OPT}" \
		--swtpm_ioctl "${SWTPM_IOCTL}" \
		--tpm2 > /dev/null; then
		echo "Error: Could not run $SWTPM_SETUP."
		echo "Setup Logfile:"
		cat "${workdir}/logfile"
		exit 1
	fi

	SWTPM_SERVER_NO_DISCONNECT=1 run_swtpm "${SWTPM_INTERFACE}" \
		--tpm2 \
		--flags not-need-init \
		--tpmstate "dir=${workdir}" \
		--log level=0

	ncat -l ${SWTPM_FAKE_CTRL_PORT} \
		-k -c "xargs --null -n1 printf '\x00\x00\x00\x00' 2>/dev/null" &
	# shellcheck disable=SC2181
	if [ $? -ne 0 ]; then
		echo "Could not start ncat"
		exit 1
	fi
	NCAT_PID=$!
	if ! kill_quiet -0 ${NCAT_PID}; then
		echo "ncat must have terminated"
		exit 1
	fi

	bash -c "tpm2-abrmd --tcti=mssim:host=127.0.0.1,port=${SWTPM_SERVER_PORT} --allow-root & echo \$! > \"${TPM2_ABRMD_PIDFILE}\"; wait" &
	BASH_PID=$!

	if wait_for_file "${TPM2_ABRMD_PIDFILE}" 3; then
		echo "Error: Could not get tpm2-abrmd's PID file"
		exit 1
	fi

	TPM2_ABRMD_PID=$(cat "${TPM2_ABRMD_PIDFILE}")
	if ! kill_quiet -0 "${TPM2_ABRMD_PID}"; then
		echo "Error: tpm2-abrmd with pid ${TPM2_ABRMD_PID} must have terminated"
		exit 1
	fi

	if ! tmp="$(tpm2_ptool init 2>&1)"; then
		echo "tpm2_ptool init failed:"
		echo "${tmp}"
		exit 1
	fi
	PID="$(echo "${tmp}" | grep -E "^id:" |cut -d ":" -f2 | tr -d " ")"
	if [ -z "${PID}" ]; then
		echo "Could not grep the pid from the tpm2_ptool output"
		echo "${tmp}"
		exit 1
	fi

	if ! tmp="$(SWTPM_PKCS11_PIN="mypin 123" SWTPM_PKCS11_SO_PIN="123" ${SWTPM_CREATE_TPMCA} \
		--dir "${SWTPM_LOCALCA_DIR}" \
		--overwrite \
		--outfile "${SWTPM_LOCALCA_CONF}" \
		--group tss \
		--tpm2 \
		${algorithm:+--algorithm "${algorithm}"} \
		--pid "${PID}" 2>&1)"; then
		echo "Error: Could not create TPM CA"
		echo "${tmp}"
		exit 1
	fi

	for fil in \
		swtpm-localca-rootca-cert.pem \
		swtpm-localca-rootca-privkey.pem \
		swtpm-localca-tpmca-cert.pem \
		swtpm-localca-tpmca-pubkey.pem; do
		if [ ! -r "${SWTPM_LOCALCA_DIR}/${fil}" ]; then
			echo "Error: TPM CA tool did not create file ${fil}."
			exit 1
		fi
	done

	for regex in \
		"^statedir = " \
		"^signingkey = " \
		"^issuercert = " \
		"^certserial = " \
		"^SWTPM_PKCS11_PIN = mypin 123"; do
		if [ -n "${regex}" ] && \
		   ! grep  -q -E "${regex}" "${SWTPM_LOCALCA_CONF}"; then
			echo "Error: Could not find regex '${regex}' in CA config file."
			cat "${SWTPM_LOCALCA_CONF}"
			exit 1
		fi
	done

	params=""
	if [ "${vtpm_is_tpm2}" -ne 0 ]; then
		params="--tpm2"
		skip=0
	else
		skip=7 # header in cert
	fi

	# make sure we can actually sign with this new certificate
	if ! ${SWTPM_LOCALCA} \
		--type ek \
		--ek x=739192d8f1004283957a7b1568d610b41c637ccc114aadcac4908c20456468fa,y=59f63ac06f8011f6fdd1460c6bc8e3e0a2d090d4fc188c7e04870e06795ce8ae \
		--dir "${workdir}" --vmid test \
		${params} \
		--tpm-spec-family 2.0 --tpm-spec-revision 146 --tpm-spec-level 00 \
		--tpm-model swtpm --tpm-version 20170101 --tpm-manufacturer IBM \
		--configfile "${SWTPM_LOCALCA_CONF}" \
		--optsfile /dev/null; then
		echo "Error: The CA could not sign with the new certificate"
		exit 1
	fi
	if [ ! -f "${workdir}/ek.cert" ]; then
		echo "Error: The CA did not produce a certificate"
		exit 1
	fi

	case "${algorithm}" in
	rsa2048)	exp_certsize=500;; # cert should be ~541 bytes long; give some 'room'
	ecc256)		exp_certsize=460;; # cert should be ~476 bytes long;    - " -
	secp384r1)	exp_certsize=510;; # cert should be ~519 bytes long;    - " -
	esac
	if [ "$(get_filesize "${workdir}/ek.cert")" -lt "${exp_certsize}" ]; then
		echo "Error: The certificate's size is dubious"
		ls -l "${workdir}/ek.cert"
		exit 1
	fi

	# Check the contents of the certificate
	certinfo=$(dd "if=${workdir}/ek.cert" bs=1 "skip=$skip" status=none | \
		   "$CERTTOOL" -i --inder)
	regexs=('^[[:space:]]+2.23.133.8.1$'
		'^[[:space:]]+directoryName:.*(,)?2.23.133.2.3=.*'
		'^[[:space:]]+directoryName:.*(,)?2.23.133.2.2=.*'
		'^[[:space:]]+directoryName:.*(,)?2.23.133.2.1=.*'
		'^[[:space:]]+Certificate Authority \(CA\): FALSE$'
		'^[[:space:]]+Unknown extension 2.5.29.9 \(not critical\):$'
		'^[[:space:]]+Hexdump: 3019301706056781050210310e300c0c03322e3002010002020092$')
	if [ "${vtpm_is_tpm2}" -ne 0 ]; then
		local alg

		case "${algorithm}" in
		rsa2048)	alg=RSA;;
		*)		alg=ECDSA;;
		esac

		# TPM 2.0; due to ecc: Key agreement
		regexs+=('^[[:space:]]+Key agreement\.$'
			 '^[[:space:]]+Signature Algorithm: '"${alg}"'-SHA256$')
	else
		regexs+=('^[[:space:]]+Key encipherment\.$'
			 '^[[:space:]]+Signature Algorithm: RSA-SHA1$')
	fi

	for ((i=0; i < ${#regexs}; i++)); do \
		if [ -n "${regexs[$i]}" ] && \
		   ! echo "${certinfo}" | grep -q -E "${regexs[$i]}"; then
			echo "Error: Could not match regex '${regexs[$i]}' with certificate info:"
			echo "${certinfo}"
			exit 1
		fi
	done

	# Send SIGTERM to tpm2-abrmd
	kill_quiet -15 "${TPM2_ABRMD_PID}"
	TPM2_ABRMD_PID=""

	kill_quiet -9 "${NCAT_PID}"
	NCAT_PID=""

	# Shut down TPM
	if ! run_swtpm_ioctl "${SWTPM_INTERFACE}" -s; then
		echo "Error: Could not shut down the ${SWTPM_INTERFACE} TPM."
		exit 1
	fi

	if wait_process_gone "${SWTPM_PID}" 4; then
		echo "Error: ${SWTPM_INTERFACE} TPM should not be running anymore."
		exit 1
	fi

	if wait_process_gone "${SWTPM_PID}" 4; then
		echo "Error: tcsd should not be running anymore."
		exit 1
	fi
	SWTPM_PID=""
} # run_test

run_test 1 "rsa2048"
echo "Test 1: OK"

run_test 1 "ecc256"
echo "Test 2: OK"

run_test 1 "secp384r1"
echo "Test 3: OK"

run_test 0
echo "Test 4: OK"

exit 0
